﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;
using ComputerArchitecture.Core.Logging;
using ComputerArchitecture.Numbers;
using ComputerArchitecture.Units.Integers;
using ComputerArchitecture.Units.Integers.Signed.Implementations;
using ComputerArchitecture.Units.Integers.Signed.Interfaces;
using ComputerArchitecture.Units.Integers.Unsigned.Implementations;
using ComputerArchitecture.Units.Integers.Unsigned.Interfaces;
using ComputerArchitecture.Units.Interfaces;

namespace ComputerArchitecture.Units.Float
{
    public class FloatDivideUnit : LoggingObject, IFloatDivideUnit
    {
        #region Fields

        private readonly ISignedSubstractUnit _signedSubstractUnit;
        private readonly ISignedAdderUnit _signedAdderUnit;
        private readonly IUnsignedSubstractUnit _unsignedSubstractUnit;
        private readonly IUnsignedDivideUnit _unsignedDivideUnit;
        private readonly IUnsignedAdderUnit _unsignedAdderUnit;
        private readonly IUnsignedCompareUnit _unsignedCompareUnit;

        #endregion

        #region Constructors

        public FloatDivideUnit(ILoggingService loggingService, ISignedSubstractUnit signedSubstractUnit, ISignedAdderUnit signedAdderUnit, IUnsignedSubstractUnit unsignedSubstractUnit,
            IUnsignedAdderUnit unsignedAdderUnit, IUnsignedDivideUnit unsignedDivideUnit, IUnsignedCompareUnit unsignedCompareUnit)
            : base(loggingService)
        {
            ArgumentValidator.NullValidate(signedSubstractUnit, "signedSubstractUnit");
            _signedSubstractUnit = signedSubstractUnit;
             ArgumentValidator.NullValidate(signedAdderUnit, "signedAdderUnit");
            _signedAdderUnit = signedAdderUnit;
            ArgumentValidator.NullValidate(unsignedSubstractUnit, "unsignedSubstractUnit");
            _unsignedSubstractUnit = unsignedSubstractUnit;
            ArgumentValidator.NullValidate(unsignedAdderUnit, "unsignedAdderUnit");
            _unsignedAdderUnit = unsignedAdderUnit;
            ArgumentValidator.NullValidate(unsignedDivideUnit, "unsignedDivideUnit");
            _unsignedDivideUnit = unsignedDivideUnit;
            ArgumentValidator.NullValidate(unsignedCompareUnit, "unsignedCompareUnit");
            _unsignedCompareUnit = unsignedCompareUnit;
        }

        public FloatDivideUnit(ILoggingService loggingService)
            : this (loggingService, new SignedSubstractUnit(loggingService), new SignedAdderUnit(loggingService), new UnsignedSubstractUnit(loggingService), new UnsignedAdderUnit(loggingService), 
            new UnsignedDivideUnit(loggingService), new UnsignedCompareUnit(loggingService))
        {
        }

        #endregion

        #region Methods

        public IFloatPointNumber Divide(IFloatPointNumber dividend, IFloatPointNumber divisor)
        {
            ArgumentValidator.NullValidate(dividend, "dividend");
            ArgumentValidator.NullValidate(divisor, "divisor");

            if (dividend.Mantissa.Size != divisor.Mantissa.Size || dividend.Exponent.Size != divisor.Exponent.Size)
            {
                throw new IncompatibleSizeException();
            }

            if (dividend.IsZero)
            {
                this.LoggingService.LogWarning("Dividend is zero. Return signed zero");
                return divisor.SignBit.IsSet
                           ? FloatPointNumber.GetNegativeZero(dividend.Exponent.Size, dividend.Mantissa.Size)
                           : FloatPointNumber.GetPositiveZero(dividend.Exponent.Size, dividend.Mantissa.Size);
            }
            if (divisor.IsZero)
            {
                this.LoggingService.LogWarning("Divisor is zero. Return signed infinity");
                return dividend.SignBit.IsSet
                           ? FloatPointNumber.GetNegativeInfinity(dividend.Exponent.Size, dividend.Mantissa.Size)
                           : FloatPointNumber.GetPositiveInfinity(dividend.Exponent.Size, dividend.Mantissa.Size);
            }
            this.LoggingService.LogInfo("Calculate sign bit as \'xor\'");
            Bit resultBit = dividend.SignBit ^ divisor.SignBit;
            this.LoggingService.LogInfo("Sign bit: {0}", resultBit.ToString());
            
            bool overflow;
            IUnsignedBitNumber offset = FloatUnit.GetUnsignedOffset(dividend.Exponent.Size);
            this.LoggingService.LogInfo("Calculate result exponent");
            _unsignedSubstractUnit.LoggingService.DisableLogging();
            ISignedBitNumber dividendExponent = _unsignedSubstractUnit.Substract(dividend.Exponent, offset, out overflow).ToSigned(),
                               divisorExponent = _unsignedSubstractUnit.Substract(divisor.Exponent, offset, out overflow).ToSigned();
            _unsignedSubstractUnit.LoggingService.EnableLogging();
            this.LoggingService.LogInfo(" get divisor exponent in complement code: {0}", dividendExponent);
            this.LoggingService.LogInfo(" get dividend exponent in complement code: {0}", divisorExponent);
            this.LoggingService.LogInfo(" substract exponents");
            _signedSubstractUnit.LoggingService.DisableLogging();
            var resultSignedExponent = _signedSubstractUnit.Substract(dividendExponent, divisorExponent, out overflow);
            _signedSubstractUnit.LoggingService.EnableLogging();
            if (overflow)
            {
                // positive - negative = +inf
                if (resultSignedExponent[0].IsSet)
                {
                    this.LoggingService.LogWarning("Overflow after adding exponents");
                    if (resultBit.IsSet)
                    {
                        this.LoggingService.LogInfo("Return negative infinity");
                        return FloatPointNumber.GetNegativeInfinity(dividend.Exponent.Size, dividend.Mantissa.Size);
                    }
                    this.LoggingService.LogInfo("Return positive infinity");
                    return FloatPointNumber.GetPositiveInfinity(divisor.Exponent.Size, divisor.Mantissa.Size);
                }
                else
                {
                    this.LoggingService.LogWarning("Underflow after adding exponents");
                    if (resultBit.IsSet)
                    {
                        this.LoggingService.LogInfo("Return negative zero");
                        return FloatPointNumber.GetNegativeZero(dividend.Exponent.Size, dividend.Mantissa.Size);
                    }
                    this.LoggingService.LogInfo("Return positive zero");
                    return FloatPointNumber.GetPositiveZero(divisor.Exponent.Size, divisor.Mantissa.Size);
                }
            }
            
            this.LoggingService.LogInfo(" result exponent: {0}", resultSignedExponent);

            IUnsignedBitNumber dividendMantissa = dividend.GetImplicitMantissa(),
                               divisorMantissa = divisor.GetImplicitMantissa();
            this.LoggingService.LogInfo("dividend implicit mantissa: {0}", dividendMantissa);
            this.LoggingService.LogInfo("divisor implicit mantissa: {0}", divisorMantissa);

            // extend dividend
            IUnsignedBitNumber n = new UnsignedBitNumber(dividendMantissa.Size << 1),
                               d = new UnsignedBitNumber(divisorMantissa.Size << 1),
                               q = new UnsignedBitNumber(dividendMantissa.Size);
            dividendMantissa.CopyTo(n, dividendMantissa.Size, 0, dividendMantissa.Size);
            divisorMantissa.CopyTo(d, divisorMantissa.Size, 0, divisorMantissa.Size);
            IUnsignedBitNumber r = (IUnsignedBitNumber) n.Clone();

            for (int i = 0; i < q.Size && !r.IsZero; ++i )
            {
                _unsignedCompareUnit.LoggingService.DisableLogging();
                int result = _unsignedCompareUnit.Compare(r, d);
                _unsignedCompareUnit.LoggingService.EnableLogging();
                if (result != -1)
                {
                    q[i].Set();
                    _unsignedSubstractUnit.LoggingService.DisableLogging();
                    r = _unsignedSubstractUnit.Substract(r, d, out overflow);
                    _unsignedSubstractUnit.LoggingService.EnableLogging();
                }
                Bit carryBit;
                r.LogicShiftLeft(1, out carryBit);

            }
            this.LoggingService.LogInfo("Result mantissa after division: {0}", q);
            if (!q[0].IsSet)
            {
                this.LoggingService.LogInfo("Need normalizing");
            }
            while (!q[0].IsSet)
            {
                Bit carryBit;
                this.LoggingService.LogInfo(" Dec result exponent");
                _signedSubstractUnit.LoggingService.DisableLogging();
                resultSignedExponent = _signedSubstractUnit.Dec(resultSignedExponent, out overflow);
                _signedSubstractUnit.LoggingService.EnableLogging();
                if (overflow)
                {
                    this.LoggingService.LogWarning(" Underflow detected. return zero");
                    if (resultBit.IsSet)
                    {
                        this.LoggingService.LogInfo(" Return negative zero");
                        return FloatPointNumber.GetNegativeZero(dividend.Exponent.Size, dividend.Mantissa.Size);
                    }
                    this.LoggingService.LogInfo(" Return positive zero");
                    return FloatPointNumber.GetPositiveZero(divisor.Exponent.Size, divisor.Mantissa.Size);
                }
                this.LoggingService.LogInfo(" New exponent: {0}", resultSignedExponent);
                this.LoggingService.LogInfo(" Shift mantissa left");
                q.LogicShiftLeft(1, out carryBit);
                this.LoggingService.LogInfo(" New mantissa: {0}", q);
            }

            _unsignedAdderUnit.LoggingService.DisableLogging();
            var resultExponent =
                _unsignedAdderUnit.Add(resultSignedExponent.ToUnsigned(), offset, out overflow);
            _unsignedAdderUnit.LoggingService.EnableLogging();
            
            IUnsignedBitNumber resultMantissa = new UnsignedBitNumber(q.Size - 1);
            q.CopyTo(resultMantissa, 0, 1, resultMantissa.Size);

            return new FloatPointNumber(resultBit, resultExponent, resultMantissa);

        }

        #endregion
    }
}
